Skip to content

Dispatch the CSA Compressor gated pooling to the cudnn-frontend fused kernels (THD) - #5984

Open
zkyue wants to merge 2 commits into
NVIDIA:devfrom
zkyue:feat/csa-fused-compressor
Open

Dispatch the CSA Compressor gated pooling to the cudnn-frontend fused kernels (THD)#5984
zkyue wants to merge 2 commits into
NVIDIA:devfrom
zkyue:feat/csa-fused-compressor

Conversation

@zkyue

@zkyue zkyue commented Jul 23, 2026

Copy link
Copy Markdown

What (reworked per review)

Addresses #5968. Reworked as discussed with @hxbai: the fused kernels themselves have
moved to cudnn-frontend — NVIDIA/cudnn-frontend#427 (merged into develop at
b950af1, 2026-07-30) adds them behind the FE-OSS
APIBase pattern (cudnn.csa.compressor, CSACompressorForward/Backward +
high-level wrappers) — and this PR is now the thin Megatron-side dispatch only:

  • Compressor._forward_thd (THD packed, non-pre-grouped path) tries the fused fast
    path for the gated-softmax pooling region — gather-index build, gather, + APE,
    overlap-window transform (coff == 2), fp32 softmax, gated weighted sum — and keeps
    the existing eager implementation as the semantic reference and fallback for every
    unsupported configuration. One fused compute kernel per direction (plus a small
    dAPE zero-init in backward) instead of ~40 forward / ~50 backward eager launches.
  • The dispatch is additive: the eager region is unchanged (re-indented only), SBHD and
    the pre-grouped CP-prep input path are untouched.

Compared to the previous revision of this PR, the ~1000-line kernel module and the
benchmark harness are gone (they live in cudnn-frontend #427, harness included in its
test/bench assets); what remains is a ~240-line dispatch shim, the csa.py edit, and a
trimmed test file: +732 / −37 vs dev (previously +1930 / −37).

Dependency

  • cudnn-frontend with the CSA compressor API provides the kernels:
    CSA: add fused Compressor forward+backward CuTe-DSL kernels (ported from Megatron-LM) cudnn-frontend#427, merged into develop at b950af1 (2026-07-30). The
    fused path activates when the installed cudnn-frontend provides cudnn.csa — i.e. a
    source install of develop >= b950af1, or the upcoming nvidia-cudnn-frontend 1.27
    pip release (expected early August) — otherwise the dispatch silently keeps the eager
    path. This includes the frontend's nvidia-cutlass-dsl runtime dependency (the
    frontend's cutedsl extra). No hard dependency is added to Megatron: availability
    is probed at dispatch time by importing the concrete entry points
    (cudnn.csa.compressor.csa_compressor_forward_wrapper / ..._backward_wrapper) —
    capability detection, not version comparison. nvidia-cudnn-frontend installs that
    predate the API simply lack cudnn.csa, and any import failure (missing package,
    missing DSL extra, partial install) silently keeps the eager path; csa.py's import
    chain never fails because of the frontend.
  • Autograd wiring stays on the Megatron side (a torch.autograd.Function around the
    frontend's forward/backward wrappers); the frontend APIs are pure
    kernels-plus-validation.

Dispatch / gating (unchanged semantics vs the previous revision)

The fast path engages only for: THD packed non-pre-grouped path, compress_ratio == 4
(coff == 2, the production CSA/HCA configuration), bf16 kv/score, fp32 ape,
compute capability 10.0 (the frontend's validated envelope), int32 flat offsets
(total_tokens * coff * head_dim < 2**31). Everything else — SBHD, the pre-grouped
CP-prep path, compress_ratio == 128 (functionally supported by the frontend kernels,
stays on eager until tuned), missing/old frontend, other devices — keeps the eager
implementation. MCORE_CSA_FUSED_COMPRESSOR=0 disables the dispatch entirely. The
dispatch also keeps the eager path under torch.use_deterministic_algorithms(True)
(dAPE is accumulated with fp32 atomics in the fused backward) and under
torch.compile tracing (the frontend launch path takes raw pointers; eager lets the
compiler fuse the region itself). CUDA graphs: capture-compatible after a one-step
eager warmup per (ratio, head_dim, coff) configuration (a first call that would JIT
under capture raises loudly, per the frontend); the dispatch passes the caller's static
fixed_total_comp capacity through without device synchronization.

Numerics

The numerics contract is the frontend kernels' (measured and tested in
NVIDIA/cudnn-frontend#427; original analysis in #5968): all arithmetic fp32 with a
single final bf16 rounding, mul.rn/fma.rn pinned in PTX. vs an fp32-intermediate
eager reference: dKV/dScore bit-identical, forward within one bf16 rounding
step. Not bit-identical to the current eager region (which rounds softmax weights to
bf16 and multiplies in bf16) but at least as accurate against an fp64 oracle on every
tested output. Forward, dKV, dScore are bitwise run-to-run deterministic; dAPE is
not (fp32 atomics) — hence the deterministic-mode fallback above. Incoming gradients on
static-capacity padding rows are ignored (they are tail padding, not consumed
downstream); never-consumed input elements get exact zeros, matching autograd. These
gates are re-verified here at the dispatch level (see Tests).

Performance

The kernels this PR dispatches to are the ones measured in NVIDIA/cudnn-frontend#427
(same B200 methodology as before; the port also picked up two additional optimization
commits there — forward 32-bit vectorized access and backward kernel-side zero-writes —
so the numbers are better than this PR's previous revision). From #427, isolated GPU
kernel time (nsys, sum of kernel durations, 50 iterations after 20 warmup; THD packs of
8192-token sequences, ratio = 4, coff = 2, bf16, vs the verbatim eager region of
Compressor._forward_thd):

THD pack head_dim eager fwd fused fwd fwd eager bwd fused bwd bwd
1×8192 128 117.8 µs 4.5 µs 26.5× 187.2 µs 12.8 µs 14.6×
3×8192 128 229.8 µs 10.0 µs 23.0× 352.7 µs 22.2 µs 15.9×
1×8192 512 263.3 µs 12.4 µs 21.2× 425.0 µs 22.8 µs 18.6×
3×8192 512 664.3 µs 35.0 µs 19.0× 1155.8 µs 66.0 µs 17.5×

Wall-clock numbers, the hardware-ceiling audit (DRAM traffic within 1% of algorithmic
bytes), and per-commit bitwise gates are in #427's description.

Tests

tests/unit_tests/transformer/experimental_attention_variant/test_csa_fused_compressor.py
— trimmed to the Megatron-side wiring (kernel-level coverage — zero-write ownership,
CUDA-graph capture/replay, determinism, coff == 1, fp64 oracle — lives in #427's test
suite):

  • numerics of the dispatched fused region vs the eager region, with the same gates as
    before (dKV/dScore bitwise vs an fp32-intermediate reference; forward within one
    bf16 rounding step; tolerance vs verbatim upstream numerics) over ragged THD packs
    including segments shorter than ratio;
  • fixed_total_comp static-capacity padding through the dispatch (forward semantics +
    ignored padding-row gradients);
  • dispatch gating and eager fallback: kill switch, missing/old cudnn-frontend (no
    cudnn.csa → silent eager, verified bitwise-identical to the kill-switch path at the
    Compressor._forward_thd level), deterministic mode (dispatch falls back; enabling
    deterministic mode between forward and backward raises loudly in the frontend),
    ratio == 128, non-bf16, unexpected layout, empty output;
  • Compressor._forward_thd integration: fused engages (spy), matches eager, gradients
    flow to inputs and ape.

The module keeps pytestmark = pytest.mark.launch_on_gb200; without CUDA, without a
cudnn-frontend that provides cudnn.csa, or off CC 10.0 every test skips cleanly (the
GB200 CI lane needs nvidia-cudnn-frontend >= 1.27 — the first release containing
#427 — plus its cutedsl extra to actually exercise the fused path; until then the
tests skip rather than fail).

Full runs on 1×B200 with cudnn-frontend develop @ b950af1 (the #427 merge)
installed from source: the new test file
8 passed; the related suites (test_attention_variant_csa.py,
test_csa_cp_layout_kernels.py, test_csa_cp_utils.py) with the fused dispatch live:
123 passed, 1 skipped (needs ≥2 ranks), 0 failed. With no cudnn-frontend installed:
8 skipped, related suites unaffected (eager path is untouched).

Notes

@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@zkyue
zkyue marked this pull request as ready for review July 23, 2026 05:16
@zkyue
zkyue requested review from a team as code owners July 23, 2026 05:16
@hxbai

hxbai commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Hi @zkyue Thanks for your contribution!

I think it is better to put the kernels in cudnn-frontend like other CSA/HCA fused kernels. Is it convenient for you to do so, or do you need our help to port the kernel?

@zkyue

zkyue commented Jul 23, 2026

Copy link
Copy Markdown
Author

@hxbai Sure, happy to do that — the kernels are self-contained CuTe DSL and should map cleanly onto the cudnn-frontend python API structure (we've contributed there before). Plan: I'll open a PR against cudnn-frontend porting the fwd+bwd kernels (APIBase-style wrapper, THD semantics, tests), then rework this PR into a thin dispatch that uses the cudnn-frontend implementation when available, keeping the eager fallback. Will link the kernel PR here once it's up.

@zkyue

zkyue commented Jul 23, 2026

Copy link
Copy Markdown
Author

@hxbai The cudnn-frontend kernel PR is up: NVIDIA/cudnn-frontend#427 (fwd+bwd kernels, APIBase-style wrapper, THD semantics, 40 tests; outputs bit-identical to the kernels in this PR). Once it lands I'll rework this PR into the thin dispatch that uses the cudnn-frontend implementation when available, keeping the eager fallback.

Anerudhan pushed a commit to NVIDIA/cudnn-frontend that referenced this pull request Jul 30, 2026
…rom Megatron-LM) (#427)

* CSA: add fused Compressor forward+backward CuTe-DSL kernels

Port the fused CSA/HCA Compressor gated-pooling kernels from Megatron-LM
(NVIDIA/Megatron-LM#5984) into the FE-OSS Python API,
per the maintainer request in
NVIDIA/Megatron-LM#5984 (comment).

One forward and one backward CuTe-DSL kernel fuse the THD gated-softmax
pooling region (gather -> +APE -> overlap-window transform -> fp32 softmax ->
gated weighted sum -> bf16 cast). Kernel math is unchanged from the Megatron
original (verified bitwise on forward/dKV/dScore); the interface is reshaped
to the APIBase pattern: CSACompressorForward / CSACompressorBackward classes,
csa_compressor_forward_wrapper / csa_compressor_backward_wrapper, a new
python/cudnn/csa package, docs, and fe_api tests (numerics vs fp32/upstream
eager references and an fp64 oracle, ragged packs, static-capacity padding,
run-to-run determinism, CUDA-graph capture/replay, check_support boundaries).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA compressor: vectorize forward bf16 access (32-bit, CuTe autovec_copy)

Widen every bf16 load/store in the fused Compressor forward kernel from
scalar (16-bit) to paired 32-bit accesses: each thread now owns 2 adjacent
head dims and moves its window slices through 32-bit universal copies
(cute.autovec_copy over register fragments; cute.assume makes the
alignment provable). The per-lane fp32 math is unchanged and in the same
order, so the forward output stays bitwise identical to the previous
kernel (re-verified against the Megatron-LM original on the same 5 THD
shape-mix gate as the original port). Odd head_dims keep a scalar
(vec == 1) instantiation of the same kernel; the head_dim 128/512
production shapes take vec == 2.

The launch schedule moves from (rows_per_cta=4, threads=128) to one row
per CTA with 64-thread column groups, which widens the sub-wave grids
that limit the small shapes.

Measured (nsys pure kernel time, B200, ratio=4 coff=2, THD packs of
8192-token sequences, forward kernel only):

  shape        before    after    speedup
  1x8192 d128   6.0 us   4.5 us   1.36x
  3x8192 d128  12.6 us  10.0 us   1.26x
  1x8192 d512  16.0 us  12.4 us   1.29x
  3x8192 d512  42.1 us  35.0 us   1.20x

Alternatives measured and rejected on the same matrix (all bitwise-equal):
a PTX-unpack vec2 prototype (slower than the pure-DSL version:
4.7/10.5/12.9/35.7 us), and 64/128/256-bit per-thread vectors, which cut
executed instructions but lose to register pressure and occupancy
(80/147/255 regs vs 48; the 256-bit variant spills). Registers stay
spill-free at 48 (previous kernel: 40).

Tests: numerics shape matrix extended with an odd head_dim case to cover
the scalar-layout instantiation, and a check_support rejection for
head_dims beyond the launch bound.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA compressor: fold dKV/dScore zero-init into the backward kernel

The backward previously required zero-initialized dKV/dScore buffers: the
kernel only wrote consumed positions, and two tensor-wide bf16 fill
kernels (torch.zeros_like) supplied the zeros everywhere else. Those
fills cost as much DRAM traffic as the kernel's own stores.

The kernel now writes every position itself: consumed positions get their
gradients as before (disjoint, atomic-free stores), and each
never-consumed slot class gets an exact zero from a unique natural owner,
keeping all stores disjoint and dKV/dScore bitwise run-to-run
deterministic:

  - first-half columns of each segment's last block's own tokens
    (coff == 2; no next block consumes them) -> that last block;
  - per-segment tail tokens (seqlen % ratio, both halves) -> the
    segment's last block;
  - all tokens of segments with zero output blocks (seqlen < ratio) ->
    CTA column bidx == 0;
  - tokens beyond cu_seqlens[-1] (static token-capacity padding of the
    gradient buffers, the CUDA-graph case) -> grid-strided across CTA
    columns.

The backward wrapper allocates grad_kv/grad_score with torch.empty_like
instead of torch.zeros_like (the fp32 dAPE fill stays: it is the atomic
accumulator). When total_comp == 0 the kernel cannot launch, so the
wrapper falls back to zeroed allocations to preserve autograd's
exact-zero semantics; the class API documents the same caveat.

Measured on the backward region (kernel + remaining fills vs kernel +
three fills before), B200, ratio=4 coff=2, THD packs of 8192-token
sequences; nsys = sum of kernel durations per iteration, wall = CUDA
events around the wrapper call:

  shape        nsys before -> after      wall before -> after
  1x8192 d128  15.5 -> 12.8 us (1.21x)   56.2 -> 47.2 us (1.19x)
  3x8192 d128  29.9 -> 22.2 us (1.35x)   61.8 -> 54.6 us (1.13x)
  1x8192 d512  34.2 -> 22.8 us (1.50x)   67.9 -> 57.3 us (1.19x)
  3x8192 d512  90.9 -> 66.0 us (1.38x)  111.6 -> 101.8 us (1.10x)

The zero-writes cost the kernel 8 registers (64 -> 72, occupancy 44% ->
41% at 3x8192 d512) and a small amount of extra store traffic, repaid
several times over by dropping the two whole-tensor fills.

dKV/dScore remain bitwise identical to the Megatron-LM original on the
5-shape port gate and to the fp32 eager reference on every test shape.

Tests: NaN-canary suite proving the kernel fully overwrites uninitialized
buffers (ragged, degenerate short segments, all-tiny packs, head_dim 512,
static-capacity padded rows) bitwise against zero-initialized runs and
the eager reference; a zeros-fallback test for packs where no segment
reaches ratio tokens. The existing CUDA-graph replay test covers the
token-capacity padding class (it fails without the grid-strided sweep).

Docs: csa.md updated (buffer contract, fill count, perf tables refreshed
for both this commit and the forward vectorization).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* Address review feedback: docs bounds, lint, warning stacklevel

- docs: list the total_comp * head_dim < 2**31 and total_comp > 0 with
  total_tokens < ratio support boundaries already enforced in check_support
- docs: run the compressor tests from test/python per repo convention
- csa/__init__.py: spell out __all__ as an explicit literal (Ruff PLE0604)
- compressor/__init__.py: sort __all__ (Ruff RUF022)
- compressor/api.py: add stacklevel=2 to the deterministic-mode RuntimeWarning

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA compressor: rewrite graph benchmark to fwd-only + fwd+bwd total graphs

Address review feedback on the PR #427 fairness benchmark (benchmark/csa/bench_csa_compressor.py).

- Drop the subtraction-derived backward-graph estimate. Graph variants are now two directly measured, symmetric columns for both eager and fused: a forward-only graph (eager under no_grad) and a forward+backward total graph.

- Capture the eager fwd+bwd graph with stable, pre-allocated zero .grad buffers (the supported torch CUDA-graph pattern): the captured region zeros them in place, then runs forward + autograd backward, so every replay accumulates into a zeroed buffer -- numerically identical to a single fresh backward. A per-shape graph-vs-fresh-backward cross-check is printed (all shapes bitwise-equal).

- The fused total graph captures the forward wrapper immediately followed by the backward wrapper.

- Emit both the per-call and graph tables from a single run; graph speedups are eager/fused of the displayed us, truncated to one decimal (never rounded up). The backward replay is reported only as ~total-fwd (a reference), never as a column.

Not collected by pytest; black -l160 clean.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* docs(csa): refresh graph table to fwd/total columns; soften wording

Address review feedback on the CSA compressor performance tables (docs/fe-oss-apis/csa.md).

- Replace the CUDA-graph table (which published a subtraction-derived backward column) with two directly measured columns: eager/fused forward-only graph and eager/fused forward+backward total graph. No backward-graph column; backward is noted only as ~total-fwd, never as a measured value.

- Refresh both the per-call and graph tables from a single run of the benchmark harness; graph speedups are eager/fused of the displayed us, truncated to one decimal.

- State that capture collapses each side's per-op launches into a single replay rather than removes launch/host overhead; say less launch-bound rather than compute-bound.

- Note how the re-run reproduces the previously published per-call wall clock, and document the eager-backward capture basis (stable zero .grad buffers).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* bench(csa): guard speedup division, dedupe leaf construction, ruff nits

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA compressor: widen the validated envelope to coff in {1, 2}

The kernels are already generic over coff — for coff=1 the window is the
block's own ratio tokens (no overlap transform, always valid); only the
check_support gate restricted the APIs to the production coff=2 form.
Widen the gate to coff in {1, 2} (ratio stays gated at 4), update the
error message and the envelope prose in api.py / docs / README, and
parametrize the tests over both coff forms: numerics vs the
fp32/upstream/fp64 eager references (the eager reference already
implements coff=1 faithfully — it skips the overlap transform), static-
capacity padding, replay determinism, empty outputs, NaN-canary
zero-writes, deterministic-mode error paths, CUDA-graph capture,
class-vs-wrapper equivalence, and check_support accept/reject
boundaries (new coff=0 / coff=3 rejects; a new empty-segment shape
covers both coff forms). No kernel changes.

Addresses hxbai's coff=1 question on the PR.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA: add missing docstrings across the compressor Python files

Bring the PR's Python docstring coverage over CodeRabbit's 80% pre-merge
gate: module docstrings for the csa package/compressor __init__ files
and one-line docstrings for the remaining undocumented helpers in
api.py (cache/ctor/compile plumbing), compressor_sm100.py (launcher
ctors), the test helpers, and the benchmark helpers. interrogate 1.7.0
over the PR's six CSA Python files: 69.4% -> 100%. Docstrings only — no
logic, kernel, or launch-path changes.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

* CSA compressor: add dedicated ratio=128 kernels (forward+backward) with ratio dispatch

The generic CSA compressor kernels keep the whole coff * ratio pooling
window in per-thread registers; past ratio ~32 that hits the
255-register wall and spills kilobytes of local memory per thread, so
ratio=128 needs dedicated kernels rather than a lifted gate. This adds
them behind the same public API: check_support now admits
ratio in {4, 128} x coff in {1, 2} (ratio=128 additionally gated to
head_dim in {128, 512}, the hardware-validated set) and the wrappers
and class APIs route to the matching kernel family by ratio
transparently.

Design: the forward streams the window with a chunked online softmax
(one CTA per output row, per-column (max, denom, acc) triples merged in
one fixed smem order); its launch schedule is bucketed by output-row
count (small/default/large, all precompiled for CUDA-graph safety) and
per bucket selects two-phase accumulation and an ex2.approx fast exp
where measured faster. The backward stages each row's window through
shared memory chunk-parallel, fuses the per-chunk den/S partial sums
into the same pass, merges partials in a fixed chunk order, and stores
gradients with a hoisted 1/den multiply — keeping kernel-side
zero-writes to never-consumed dKV/dScore slots and fp32-atomic dAPE
exactly as at ratio=4. ptxas (sm_100a): forward 32-51 registers,
backward 48-128, 0 spill / 0 stack across all 16 shipped
(config, schedule) kernels (reproducer:
benchmark/csa/reg_probe_csa_compressor_r128.py).

Numerics contract, per ratio family (docs/fe-oss-apis/csa.md): ratio=4
keeps its bitwise contract UNCHANGED — dKV/dScore bit-identical to the
fp32-intermediate eager reference, at coff 1 and 2. ratio=128 is
deterministic + faithful to that same fp32-intermediate eager
reference: bitwise run-to-run determinism of out/dKV/dScore
(NaN-prefill replay tested); the same values within final-bf16 rounding
at the gate tolerances (differing elements <= max(1, 0.1%), max_abs <=
1.6e-2, calibrated on the gate's documented input distribution — bf16
deviations scale 2^k with 2^k inputs while the fp32 intermediates stay
finite); the eager reference's non-finite propagation (finite inputs
that overflow its fp32 intermediates poison both sides alike, with the
stated caveat that the fused order saturates earlier near fp32 max —
un-normalized chunk partials); and, on inputs whose fp32 intermediates
stay finite in both evaluation orders, fp64-oracle parity (at least as
close to an fp64 oracle as the eager reference). The reduction reorders
and fast-exp buckets were each adopted on a measured win and are
covered by that contract.

Performance (nsys isolated GPU kernel time, 1x B200, vs the
fp32-intermediate eager reference region on identical inputs): forward
13.2-44.4x, backward 7.4-21.9x across coff {1, 2} x head_dim {128, 512}
x 8k-131k tokens; e.g. coff=2, d=128, 65k tokens: fwd 726.3 -> 16.4 us
(44.4x), bwd 960.0 -> 61.2 us (15.7x). Full tables with methodology in
docs/fe-oss-apis/csa.md.

Validation (B200, CC 10.0): the committed 21-case contract gate
(benchmark/csa/gate_csa_compressor_r128.py) passes 21/21 with shipped
schedule coverage asserted (forward 10/10, backward 6/6 buckets);
pytest fe_api/csa/test_CSA_compressor.py: 88 passed, 1 skipped at the
default L0 level and 98 passed, 1 skipped with -m "L0 or L1" (both
ratio families; the ratio=4 rows, including the coff=1 envelope, stay
on their bitwise assertions). New tests cover the ratio=128 dispatch
envelope (schedule selection at every nb_total bucket boundary; every
shipped (config, schedule) kernel executed against the full contract at
L1), exact-zero assertions on every never-consumed dKV/dScore slot
class in the NaN-canary, CUDA-graph capture/replay for the new family,
and grad_ape zeroing-ownership regressions.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

---------

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
zkyue added 2 commits July 30, 2026 07:13
…ressor

The fused forward+backward kernels for the gated-pooling region of
Compressor._forward_thd now live in the cudnn-frontend Python package
(cudnn.csa.compressor, cudnn-frontend PR NVIDIA#427). Megatron-side this is a
thin additive dispatch: maybe_compress_thd_fused() returns the pooled
tensor when the frontend is importable and the configuration is
supported (THD non-pre-grouped path, compress_ratio == 4, bf16
kv/score, fp32 ape, compute capability 10.0, int32 flat offsets) and
None otherwise, in which case the eager region runs unchanged. SBHD and
the pre-grouped CP-prep path are untouched.

Frontend availability is probed by importing the concrete entry points
(csa_compressor_forward_wrapper / csa_compressor_backward_wrapper), not
by version comparison: nvidia-cudnn-frontend installs that predate the
CSA compressor API lack cudnn.csa and keep the eager path, as does any
import failure (e.g. the DSL extra missing). The dispatch also keeps
the eager path under torch.use_deterministic_algorithms(True) (dAPE
fp32 atomics) and under torch.compile tracing (raw-pointer launch
path is not traceable). MCORE_CSA_FUSED_COMPRESSOR=0 disables the
dispatch entirely. compress_ratio == 128 is functionally supported by
the frontend kernels but stays on the eager path until tuned (see
issue NVIDIA#5968).

Addresses NVIDIA#5968.

Signed-off-by: zky <kaiyue.zhou@z.ai>
The kernels themselves are validated in cudnn-frontend (PR NVIDIA#427); these
tests cover the Megatron-side wiring: numerics of the dispatched fused
region vs an fp32-intermediate eager reference (bitwise dKV/dScore,
forward within one bf16 rounding step) and vs the verbatim upstream
eager numerics (tolerance) over ragged THD packs including segments
shorter than ratio; fixed_total_comp static-capacity padding rows
(including that nonzero padding-row gradients are ignored); dispatch
gating and eager fallback (kill switch, missing/old cudnn-frontend,
deterministic mode, ratio 128, non-bf16, layout, empty output), plus
Compressor._forward_thd-level integration (fused engages and matches
eager, bitwise eager fallback without the frontend, gradients flow).
The module is marked launch_on_gb200 for the CC 10.0 CI lane and skips
cleanly without CUDA, without a cudnn-frontend that provides cudnn.csa,
or off compute capability 10.0.

Addresses NVIDIA#5968.

Signed-off-by: zky <kaiyue.zhou@z.ai>
@zkyue
zkyue force-pushed the feat/csa-fused-compressor branch from 8217542 to e073b28 Compare July 30, 2026 07:36
@zkyue zkyue changed the title Add fused CuTe-DSL forward+backward kernels for the CSA Compressor gated pooling (THD) Dispatch the CSA Compressor gated pooling to the cudnn-frontend fused kernels (THD) Jul 30, 2026
@zkyue

zkyue commented Jul 30, 2026

Copy link
Copy Markdown
Author

cudnn-frontend #427 merged this morning (develop @ b950af1), so this PR is now reworked to the thin dispatch as discussed: the kernel module and the benchmark harness live upstream in NVIDIA/cudnn-frontend#427, and what remains here is the ~240-line dispatch shim, the csa.py edit, and the trimmed test file — +732/−37 vs dev (previously +1930/−37).

Force-pushed 82175427ee073b2822 (rebased onto current dev); title and PR body updated accordingly. Gates re-run against merged develop @ b950af1: dispatch tests 8 passed (8 skipped when no cudnn-frontend with cudnn.csa is on the path — silent eager fallback, no hard dependency), related suites 123 passed / 1 skipped, pinned lint clean.

@hxbai

hxbai commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

/claude strict-review

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict review passed — no significant issues found.

Scope: thin, additive dispatch shim (+732/−37) routing the THD non-pre-grouped CSA gated-pooling region to the cudnn-frontend fused kernels, with the eager region preserved verbatim as the reference/fallback.

Verified sound:

  • Autograd arityforward takes 9 non-ctx args; backward returns exactly 9 grads (3 real + 6 None). Correct.
  • Shape parity — fused out.unsqueeze(1)(total_comp, 1, head_dim), identical to the eager (kv_grouped * weights).sum(dim=1).
  • Gating invariantsratio == 4 ⟺ coff == 2 ⟺ overlap are locked together in __init__, so the fused path cannot be reached with a coff/overlap mismatch. Gates also correctly cover dtype (bf16 kv/score, fp32 ape), device (CC 10.0, cached), int32-offset overflow (total * coff * head_dim < 2**31), deterministic mode, and torch.compile tracing.
  • No hot-path sync — all dispatch checks are host-side (.shape, env read, cached capability lookup); no CPU↔GPU sync introduced. On non-CC-10.0 hardware the probe short-circuits to eager after a one-time cached import.
  • Import safety — the frontend import is lazy inside _get_frontend(); csa.py's import chain never fails on a missing/old/partial frontend.
  • Backward compat — no renamed/removed args, no checkpoint or API signature changes. H100/A100 (CC ≠ 10.0) stay bit-identical on eager. The dsv4_hybrid_fused functional case is commented out in the gb200 recipe, so no golden-value impact.
  • Unused-variable check — all new identifiers have real use paths; _frontend_error is a documented debugging aid.

One point for maintainer decision (not a defect): the fused path is on by default, so GB200 users with cudnn-frontend ≥ 1.27 get a silent numerics change (fp32-intermediate vs the eager bf16-weight multiply — documented as at-least-as-accurate against an fp64 oracle). The author has already flagged this and offered to flip to opt-in; noting only so the choice is made deliberately.

Risk: low. Additive, capability-gated, no-op against any cudnn-frontend predating the CSA API, and covered by the new dispatch/gating/fallback tests. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants